#include "ET++.ph"
#ifdef __GNUG__
#pragma implementation
#endif

#include "Clipper.h"

#include "Class.h"
#include "Math.h"
#include "View.h"
#include "Error.h"
#include "Token.h"
#include "Window.h"
#include "String.h"
#include "WindowPort.h"
#include "Collection.h"

//---- Clipper -----------------------------------------------------------------

NewMetaImpl(Clipper,VObject, (TP(vop), TB(deleteVop), T(minExtent), T(shift),
					T(relOrigin), T(scale), TP(bgcolor)));

Clipper::Clipper(VObject *vp, const Point &minsize, int id, Ink *bg)
							: VObject(minsize, id)
{
	if (bg == 0)
		bg= gInkWhite;
	bgcolor= bg;
	vop= 0;
	relOrigin= gPoint0;
	scale= 1.0;
	SetMinExtent(minsize);
	ResetFlag(eVObjOpen);
	deleteVop= FALSE;
	Add(vp);
}

Clipper::~Clipper()
{
	if (vop) {
		VObject *tmp= vop;
		vop= 0;
		tmp->ClearContainer(this);
		if (deleteVop)
			delete tmp;
	}
}

void Clipper::Open(bool mode)
{
	if (IsOpen() != mode) {
		VObject::Open(mode);
		if (vop)
			vop->Open(mode);
	}
}

void Clipper::Add(VObject *vp)
{
	Remove(vop);
	vop= vp;
	if (vop) {
		vop->SetContainer(this);
		if (IsOpen() && !vop->IsOpen())
			vop->Open(TRUE);
		ViewSizeChanged();
	}
}

VObject *Clipper::Remove(VObject *vp)
{
	if (vop && vop == vp) {
		vop= 0;
		if (vp->IsOpen())
			vp->Open(FALSE);
		vp->ClearContainer(this);
		return vp;
	}
	return 0;
}

void Clipper::SetContainer(VObject *v)
{
	VObject::SetContainer(v);
	relOrigin= gPoint0;
	if (vop && !vop->IsKindOf(View)) {
		vop->CalcExtent();
		vop->SetOrigin(gPoint0);
	}
}

void Clipper::Enable(bool b, bool redraw)
{
	VObject::Enable(b, redraw);
	if (vop)
		vop->Enable(b, redraw);
}

void Clipper::ScaleUp(Point &p)
{
	p.x= (int) ((float)p.x * scale + 0.5);
	p.y= (int) ((float)p.y * scale + 0.5);
}

void Clipper::ScaleDown(Point &p)
{
	p.x= (int) ((float)p.x / scale + 0.5);
	p.y= (int) ((float)p.y / scale + 0.5);
}

Point Clipper::GetViewSize()
{
	if (vop) {
		Point p(vop->GetExtent()-shift);
		ScaleUp(p);
		return p;
	}
	return gPoint0;
}

Rectangle Clipper::GetViewedRect()
{
	return Rectangle(relOrigin, contentRect.extent);
}

void Clipper::SetZoom(float zoom)
{
	scale= zoom;
	ViewSizeChanged();
	ForceRedraw();
}

Metric Clipper::GetMinSize()
{
	Point me(minExtent);
	//Point me(30);
	if (vop && (minExtent.x == -1 || minExtent.y == -1)) {
		Point e= vop->GetMinSize().extent;
		if (minExtent.x == -1)
			me.x= e.x;
		if (minExtent.y == -1)
			me.y= e.y;
	}
	//if (vop)
	//    return Metric(me, vop->Base());
	return me;
}

void Clipper::SetMinExtent(const Point &e)
{
	minExtent= e;
	if (e.x == -1)
		SetFlag(eVObjHFixed);
	if (e.y == -1)
		SetFlag(eVObjVFixed);
}

void Clipper::ViewSizeChanged()
{
	if (vop) {
		Point newsize(vop->GetExtent()-shift);
		ScaleUp(newsize);
		Send(GetId(), cPartViewSize, &newsize);
		Normalize();
	}
}

void Clipper::InvalidateViewRect(Rectangle r)
{
	r.origin= ContainerPoint(r.origin);
	ScaleUp(r.extent);
	InvalidateRect(r);
}

void Clipper::SetExtent(Point e)
{
	VObject::SetExtent(e);
	Normalize();
}

void Clipper::SetOrigin(Point at)
{
	VObject::SetOrigin(at);
	if (vop)
		vop->SetOrigin(gPoint0);
}

void Clipper::Normalize()
{
	Point vs(GetViewSize()), ee(GetExtent()), scroll;

	if (vs.x < ee.x)
		scroll.x= -relOrigin.x;
	else if (vs.x < relOrigin.x+ee.x)
		scroll.x= relOrigin.x+ee.x - vs.x;

	if (vs.y < ee.y)
		scroll.y= -relOrigin.y;
	else if (vs.y < relOrigin.y+ee.y)
		scroll.y= relOrigin.y+ee.y - vs.y;

	Scroll(cPartScrollRel, scroll);
}

void Clipper::Focus()
{
	if (GetContainer()) {
		GetContainer()->Focus();
		GrClipFurther(contentRect);
		GrTranslate(Offset());
		GrScale(scale, scale);
	}
}

void Clipper::DrawInner(Rectangle r, bool)
{
	if (bgcolor && bgcolor != ePatNone && !gPrinting)
		GrPaintRect(r, bgcolor);
		
	if (vop) {
		GrState gs;
		gs.Use();
		GrClipFurther(r);
		GrTranslate(Offset());
		GrScale(scale, scale);

		r.origin-= Offset();
		ScaleDown(r.origin);
		ScaleDown(r.extent);

		vop->DrawAll(r, FALSE);
	}
}

VObject *Clipper::Detect(BoolFun find, void *arg)
{
	VObject *v;
	if (vop && (v= vop->Detect(find, arg)))
		return v;
	return VObject::Detect(find, arg);
}

void Clipper::ReadEvent(Token &t)
{
	VObject::ReadEvent(t);
	t.Pos-= Offset();
	ScaleDown(t.Pos);
}

Command *Clipper::DispatchEvents(Point lp, Token &t, Clipper *vf)
{
	Command *cmd= 0;

	lp-= Offset();
	ScaleDown(lp);

	if (vop)
		cmd= vop->Input(lp, t, this);
	else
		cmd= VObject::DispatchEvents(lp, t, vf);
	if (cmd)
		PerformCommand(cmd);
	return gNoChanges;
}

Point Clipper::ContainerPoint(Point p)
{
	ScaleUp(p);
	return p+Offset();
}

void Clipper::SetBgInk(Ink *ip)
{
	bgcolor= ip;
	ForceRedraw();
}

Ink *Clipper::GetBgInk()
{
	return bgcolor;
}

//---- Scrolling ---------------------------------------------------------------

void Clipper::ConstrainScroll(Point *newOrigin)
{
	if (vop && vop->IsKindOf(View))
		((View*)vop)->ConstrainScroll(newOrigin);
}

Point Clipper::AutoScroll(Point p)
{
	ScaleUp(p);
	p-= relOrigin;
	Point scroll= Rectangle(contentRect.extent).Constrain(p);
	Scroll(cPartScrollRel, scroll-p);
	return scroll;
}

void Clipper::RevealRect(Rectangle r, Point minToSee)
{
	r.origin-= shift;
	ScaleUp(r.extent);
	ScaleUp(minToSee);
	VObject::RevealRect(Rectangle(ContainerPoint(r.origin), r.extent), minToSee);
	ScaleUp(r.origin);

	Rectangle vr(GetViewedRect());
	Rectangle ir(Inter(r, vr));

	if (ir.IsEmpty() || ir.extent.x < minToSee.x || ir.extent.y < minToSee.y)
		Scroll(cPartScrollRel, r.AmountToTranslateWithin(vr));
}

void Clipper::RevealAlign(Rectangle r, VObjAlign al)
{
	r.origin-= shift;
	ScaleUp(r.origin);
	ScaleUp(r.extent);

	Rectangle vr(GetViewedRect());
	Rectangle ir(Inter(r, vr));

	if (ir.extent.x < r.extent.x || ir.extent.y < r.extent.y) {
		Point scroll;

		switch (al & eVObjH) {
		case eVObjHCenter:
			scroll.x= (vr.Center().x - r.Center().x)/2;
			break;
		case eVObjHRight:
			scroll.x= vr.NE().x - r.NE().x;
			break;
		default:
			scroll.x= vr.NW().x - r.NW().x;
			break;
		}
		switch (al & eVObjV) {
		case eVObjVCenter:
			scroll.y= (vr.Center().y - r.Center().y)/2;
			break;
		case eVObjVBottom:
			scroll.y= vr.SW().y - r.SW().y;
			break;
		default:
			scroll.y= vr.NW().y - r.NW().y;
			break;
		}
		Scroll(cPartScrollRel, scroll);
	}
}

void Clipper::ExtentChanged(VObject*)
{
	ViewSizeChanged();
}

void Clipper::Control(int id, int part, void *val)
{
	if (part == cPartExtentChanged) {
		if (val == vop)
			ViewSizeChanged();
	}
	VObject::Control(id, part, val);
}

void Clipper::SendDown(int id, int part, void *val)
{
	if (vop)
		vop->SendDown(id, part, val);
}

Point Clipper::CalcScrollAmount(int mode, const Point &scr, const Rectangle &r)
{
	switch(mode) {
	case cPartScrollStep:
		return 32 * -scr;
	case cPartScrollPage:
		return (r.extent-32) * -scr;
	case cPartScrollAbs:
		return relOrigin - scr;
	case cPartScrollHAbs:
		return Point(r.origin.x - scr.x, 0);
	case cPartScrollVAbs:
		return Point(0, r.origin.y - scr.y);
	case cPartScrollRel:
		return scr;
	}
	return gPoint0;
}

void Clipper::Scroll(int mode, Point scroll, bool redraw)
{
	Point leftCorner, newOrigin;

	if (GetContainer() == 0)
		return;

	Point delta(CalcScrollAmount(mode, scroll, GetViewedRect()));
	/*
	switch(mode) {
	case cPartScrollStep:
		delta= 32 * -scroll;
		if (vop && vop->IsKindOf(View))
			((View*)vop)->CalcScrollStep(delta, relOrigin, scroll);
		break;
	case cPartScrollPage:
		delta= (contentRect.extent-32) * -scroll;
		break;
	case cPartScrollAbs:
		delta= relOrigin - scroll;
		break;
	case cPartScrollHAbs:
		delta.x= relOrigin.x - scroll.x;
		break;
	case cPartScrollVAbs:
		delta.y= relOrigin.y - scroll.y;
		break;
	case cPartScrollRel:
		delta= scroll;
		break;
	}
	*/

	leftCorner= Max(gPoint0, GetViewSize() - contentRect.extent);
	newOrigin= relOrigin-delta;
		
	ConstrainScroll(&newOrigin);

	delta= relOrigin - Min(Max(gPoint0, newOrigin), leftCorner);

	if (delta == gPoint0)
		return;
		
	if (! IsOpen())
		redraw= FALSE;
		
	if (redraw) {
		UpdateEvent();
		Focus();
		relOrigin-= delta;
		((WindowPort*)GrGetPort())->Scroll(delta);
	} else
		relOrigin-= delta;
	Send(GetId(), cPartScrollPos, &relOrigin);
	UpdateEvent();
}

// returns TRUE on exit loop
bool Clipper::TrackOnce(Command** tracker, TrackPhase atp, Point ap, Point pp, Point lp)
{
	Command *newTracker;

	if (*tracker == 0) {
		*tracker= gNoChanges;
		return TRUE;
	}

	newTracker= (*tracker)->TrackMouse(atp, ap, pp, lp);
	if (newTracker != *tracker) {
		if (*tracker && *tracker != gNoChanges)
			delete *tracker;
		*tracker= newTracker;
		return (newTracker == gNoChanges);
	}
	return FALSE;
}

// called from View
void Clipper::DrawInFocus(FeedbackFunc of, Command *tr,
                                const Point &ap, const Point &pp, bool turniton)
{
        if (IsOpen()) {
                Focus();
                of(this, tr, ap, pp, turniton);
	}
}

void ClipperFeedback(Clipper *cl, Command *tr, const Point &ap, const Point &pp,
                                                                bool turniton)
{
        cl->Feedback(tr, ap, pp, turniton);
}

void Clipper::Feedback(Command *tracker, const Point &ap, const Point &pp, bool turniton)
{
        Ink *pat= ePatBlack;
        if (tracker->TestFlag(eCmdFullScreen) ||
                        ! tracker->TestFlag(eCmdNoReplFeedback))
                pat= ePatXor;
        GrSetInk(pat);
        GrSetPenInk(pat);
        GrSetTextInk(pat);
        GrSetPenSize(1);
        tracker->TrackFeedback(ap, pp, turniton);
        UpdateEvent();
}

void Clipper::FeedbackOnce(Command *tracker, const Point &ap, const Point &pp, bool turniton)
{
        if (!tracker->TestFlag(eCmdNoReplFeedback) && vop && vop->IsKindOf(View))
                ((View*)vop)->ShowInAllClippers(ClipperFeedback,
                                        this, tracker, ap, pp, turniton);
        else
                Feedback(tracker, ap, pp, turniton);
        Port::FlushAnyText();
}

extern int gTimeOut;

Command *Clipper::TrackInContent(Point lp, Token token, Command* tracker)
{
	static int level= 0;
	static bool haveSeenUp;

	bool autoscroll= FALSE,
		havereset,
		isidle= FALSE,
		fullscreen= tracker->TestFlag(eCmdFullScreen),
		done;
	Point newlp, ap, pp, oldrelorigin;
	Window *bwin= GetWindow();

	level++;
	if (level == 1)
		haveSeenUp= FALSE;
	Focus();

	if (fullscreen)
		bwin->Fullscreen(TRUE);
	havereset= FALSE;

restart2:
	ap= pp= lp;
	tracker->TrackConstrain(ap, pp, &lp);
	ap= pp= lp;

	if (TrackOnce(&tracker, eTrackPress, ap, pp, lp))
		goto out;
	FeedbackOnce(tracker, ap, pp, TRUE);

restart:
	while (IsOpen()) {
		if (level > 0 && haveSeenUp && !tracker->TestFlag(eCmdMoveEvents))
			break;

		done= FALSE;
		while (!done) {
			if (isidle || autoscroll) {
				gTimeOut= 0;
				ReadEvent(token);   // poll
			} else {
				gTimeOut= 100;
				ReadEvent(token);   // block 100 ms
			}

			switch (token.Code) {
		
			case -1:
				if (autoscroll) {
					token.Code= eEvtLocMove;    // simulate a move event
					done= TRUE;
					break;
				}
				if (tracker->TestFlag(eCmdIdleEvents)) {
					done= isidle= TRUE;
					break;
				}
				//done= haveSeenUp= TRUE;
				break;
				
			case eEvtRightButton:
			case eEvtMiddleButton:
			case eEvtLeftButton:
				if (token.Flags & eFlgButDown)
					haveSeenUp= TRUE;
				else
					goto restart2;
				done= TRUE;             // return button event immediately
				break;
				
			case eEvtLocMove:
				done= TRUE;
				break;
				
			default:
				break;
			}
		}

		lp= token.Pos;
		
		if (haveSeenUp)
			break;
		
		FeedbackOnce(tracker, ap, pp, FALSE);   // clear previous feedback
		
		tracker->TrackConstrain(ap, pp, &lp);
		if (! fullscreen) {
			oldrelorigin= relOrigin;
			newlp= AutoScroll(lp);
			if (relOrigin != oldrelorigin) {
				autoscroll= TRUE;
				lp= newlp + relOrigin;
			} else
				autoscroll= FALSE;
		}
		oldrelorigin= relOrigin;
		if (TrackOnce(&tracker,
				token.Code == eEvtLocMove ? eTrackMove : eTrackIdle, ap, pp, lp))
			goto out;
		
		pp= lp;
		FeedbackOnce(tracker, ap, pp, TRUE);    // new feedback
	}
	FeedbackOnce(tracker, ap, pp, FALSE);   // clear last feedback
	tracker->TrackConstrain(ap, pp, &lp);
		
	if (! tracker->TestFlag(eCmdMoveEvents)) {
		if (fullscreen)
			bwin->Fullscreen(FALSE);
		havereset= TRUE;
		level--;
	}
				
	TrackOnce(&tracker, IsOpen() ? eTrackRelease : eTrackExit, ap, pp, lp);

	if (tracker->TestFlag(eCmdMoveEvents)) {
		haveSeenUp= FALSE;
		goto restart;
	}
out:
	Focus();
	if (! havereset) {
		if (fullscreen)
			bwin->Fullscreen(FALSE);
		level--;
	}
	return tracker;
}

OStream& Clipper::PrintOn(OStream &s)
{
	VObject::PrintOn(s);
	return s << minExtent SP << scale SP << vop SP << bgcolor SP;
}

IStream& Clipper::ReadFrom(IStream &s)
{
	VObject::ReadFrom(s);
	s >> minExtent >> scale >> vop >> bgcolor;
	Add(vop);
	SetContentRect(contentRect, TRUE);
	return s;
}

void Clipper::InspectorId(char *buf, int sz)
{
	if (vop)
		vop->InspectorId(buf, sz);
	else
		VObject::InspectorId(buf, sz);
}

void Clipper::CollectParts(Collection* col)
{
	VObject::CollectParts(col);
	if (vop)
		col->Add(vop);
}
